use core::ops::Deref;

#[cfg(feature = "alloc")]
use alloc::borrow::ToOwned;

#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use utils::bincodec::Decode;

use crate::package::ty::{NOTIFY, REQ, SIMPLE_REQ};
use crate::proto::MIN_CMD_LEN;
use crate::ProtoDecode;

#[cfg(feature = "alloc")]
pub struct Message {
    buf: Vec<u8>,
}

#[cfg(feature = "alloc")]
impl Message {

    pub fn as_ref<'a>(&'a self) -> MessageRef<'a> {
        MessageRef { buf: &self.buf }
    }
}

#[derive(Clone, Copy)]
pub struct MessageRef<'a> {
    buf: &'a [u8],
}

impl<'a> Deref for MessageRef<'a> {
    type Target = [u8];
    fn deref(&self) -> &Self::Target {
        self.buf
    }
}

impl<'a> MessageRef<'a> {
    #[cfg(feature = "alloc")]
    pub fn to_owned(&self) -> Message {
        Message {
            buf: self.buf.to_owned(),
        }
    }

    pub const unsafe fn unchecked_new(buf: &'a [u8]) -> Self {
        Self { buf }
    }

    #[inline(always)]
    pub fn ty(&self) -> u8 {
        self.buf[3]
    }

    #[inline(always)]
    pub fn seq(&self) -> u8 {
        self.buf[4]
    }

    #[cfg(feature = "address_proto")]
    pub fn src(&self) -> u8 {
        self.buf[5]
    }

    #[cfg(feature = "address_proto")]
    pub fn dst(&self) -> u8 {
        self.buf[6]
    }

    pub fn is_valid_cmd(&self) -> bool {
        let ty = self.ty();
        (self.buf.len() >= MIN_CMD_LEN) &&
        ((ty == SIMPLE_REQ) || (ty == REQ) || ty == NOTIFY)
    }

    pub fn into_req(self) -> ReqRef<'a> {
        ReqRef { msg: self }
    }
}

#[derive(Clone, Copy)]
pub struct ReqRef<'a> {
    msg: MessageRef<'a>,
}

impl<'a> Deref for ReqRef<'a> {
    type Target = MessageRef<'a>;
    fn deref(&self) -> &Self::Target {
        &self.msg
    }
}

impl<'a> ReqRef<'a> {
    pub fn cmd(&self) -> u8 {
        #[cfg(feature = "address_proto")]
        return self.msg.buf[7];

        #[cfg(not(feature = "address_proto"))]
        return self.msg.buf[5];
    }

    pub fn body(&self) -> &'a [u8] {
        let len = self.msg.len();
        #[cfg(feature = "address_proto")]
        return &self.msg.buf[8..(len - 2)];

        #[cfg(not(feature = "address_proto"))]
        return &self.msg.buf[6..(len - 2)];
    }
}

impl<'de, 'a: 'de> ReqRef<'a> {
    pub fn parse_with_decode<T: Decode<'de>>(&self) -> Option<T> {
        T::from_bytes(self.body())
    }
}

impl<'de, 'a: 'de> ReqRef<'a> {
    pub fn parse<T: ProtoDecode<'de>>(&self) -> Option<T> {
        T::decode(self.body())
    }
}
